package sharetime.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("unchecked")
public class GeneralUtil {
	private static final Logger logger = LoggerFactory.getLogger(GeneralUtil.class);

	private static DecimalFormat defaultDecimalNumberFormatter;

	private static DecimalFormat defaultLongNumberFormatter;

	private static SimpleDateFormat defaultDateFormatter;

	private static SimpleDateFormat defaultDateTimeFormatter;

	private static SimpleDateFormat defaultTimeFormatter;

	private static String versionNumber;

	private static Date buildDate;

	private static String buildNumber;

	private static String buildPartner;

	private static String buildExcludedLocale;

	private static Properties formattingProperties = new Properties();

	private static final String EMAIL_PATTERN_STRING = "([\\w-%\\+\\.]+@[\\w-%\\.]+\\.[\\p{Alpha}]+)";

	private static final Pattern EMAIL_PATTERN = Pattern
			.compile("([\\w-%\\+\\.]+@[\\w-%\\.]+\\.[\\p{Alpha}]+)");

	private static SimpleDateFormat blogDateFormatter = new SimpleDateFormat(
			"dd MMM");

	private static SimpleDateFormat blogTimeFormatter = new SimpleDateFormat(
			"h:mm a");

	private static void hackUrlEncoder() {
		try {
			Field dontNeedEncodingField = (java.net.URLEncoder.class)
					.getDeclaredField("dontNeedEncoding");
			dontNeedEncodingField.setAccessible(true);
			BitSet dontNeedEncoding = (BitSet) dontNeedEncodingField
					.get(java.net.URLEncoder.class);
			dontNeedEncoding.set(126);
			dontNeedEncoding.set(64);
		} catch (Throwable t) {
			logger.info("URLEncoder hack failed. Really nothing to worry about: "
					+ t.getMessage(), t);
		}
	}

	private static String defaultPropertyCheck(String property) {
		if (property.startsWith("@") && property.endsWith("@"))
			return null;
		else
			return property;
	}

	private static Date convertToDateWithEnglishLocale(String buildDateString) {
		DateFormat formatter;
		if (buildDateString == null)
			throw new IllegalArgumentException(
					"parameter buildDateString may not be null.");
		formatter = new SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH);
		formatter.setLenient(false);
		try {
			return formatter.parse(buildDateString.toString());
		} catch (ParseException e) {

			logger.info("Could not parse : " + buildDateString + " : " + e, e);
			return Calendar.getInstance(Locale.ENGLISH).getTime();
		}
	}

	public static String getDefaultTimeFormatterPattern() {
		return formattingProperties.getProperty("time.format", "hh:mm a");
	}

	public static void setDefaultTimeFormatterPattern(
			String defaultTimeFormatterPattern) {
		formattingProperties.setProperty("time.format",
				defaultTimeFormatterPattern);
		defaultTimeFormatter = (SimpleDateFormat) createDateFormatter(defaultTimeFormatterPattern);
	}

	public static String getDefaultDateTimeFormatterPattern() {
		return formattingProperties.getProperty("datetime.format",
				"MMM dd, yyyy HH:mm");
	}

	public static void setDefaultDateTimeFormatterPattern(
			String defaultDateTimeFormatterPattern) {
		formattingProperties.setProperty("datetime.format",
				defaultDateTimeFormatterPattern);
		defaultDateTimeFormatter = (SimpleDateFormat) createDateFormatter(defaultDateTimeFormatterPattern);
	}

	public static String getDefaultDateFormatterPattern() {
		return formattingProperties.getProperty("date.format", "MMM dd, yyyy");
	}

	public static void setDefaultDateFormatterPattern(
			String defaultDateFormatterPattern) {
		formattingProperties.setProperty("date.format",
				defaultDateFormatterPattern);
		defaultDateFormatter = (SimpleDateFormat) createDateFormatter(defaultDateFormatterPattern);
	}

	public static String getDefaultLongNumberFormatterPattern() {
		return formattingProperties.getProperty("long.number.format",
				"###############");
	}

	public static void setDefaultLongNumberFormatterPattern(
			String defaultLongNumberFormatterPattern) {
		formattingProperties.setProperty("long.number.format",
				defaultLongNumberFormatterPattern);
		defaultLongNumberFormatter = new DecimalFormat(
				defaultLongNumberFormatterPattern);
	}

	public static String getDefaultDecimalNumberFormatterPattern() {
		return formattingProperties.getProperty("decimal.number.format",
				"###############.##########");
	}

	public static void setDefaultDecimalNumberFormatterPattern(
			String defaultDecimalNumberFormatterPattern) {
		formattingProperties.getProperty("decimal.number.format",
				defaultDecimalNumberFormatterPattern);
		defaultDecimalNumberFormatter = new DecimalFormat(
				defaultDecimalNumberFormatterPattern);
	}

	public static String getStackTrace(Throwable t) {
		if (t == null) {
			return "";
		} else {
			StringWriter sw = new StringWriter();
			t.printStackTrace(new PrintWriter(sw));
			return sw.toString();
		}
	}

	public static String format(Number num) {
		if (!(num instanceof Double) && !(num instanceof BigDecimal)
				&& !(num instanceof Float)) {

			return defaultDecimalNumberFormatter.format(num);
		} else {
			return defaultLongNumberFormatter.format(num);
		}
	}

	public static String format(Date date) {
		try {
			return defaultDateFormatter.format(date);
		} catch (Exception e) {
			return "";
		}
	}

	public static String format(String str) {
		return str == null ? "" : str;
	}

	public static String format(Object obj) {
		try {
			if (obj instanceof Number)
				return format((Number) obj);
			if (obj instanceof Date)
				return format((Date) obj);
			if (obj instanceof String)
				return format((String) obj);
			return obj.toString();
		} catch (Exception e) {
			return "";
		}
	}

	public static Date convertToDate(Object obj) {
		if (obj instanceof Date)
			return (Date) obj;
		Date date = null;
		if (date == null)
			try {
				date = defaultDateFormatter.parse(obj.toString());
			} catch (ParseException e) {
				logger.info("Could not parse : " + obj + " : " + e, e);
			}
		return date;
	}

	public static Long convertToLong(Object obj) {
		if (obj instanceof Long)
			return (Long) obj;
		else {
			try {
				return new Long(defaultLongNumberFormatter
						.parse(obj.toString()).longValue());
			} catch (Exception e) {
				return null;
			}
		}
	}

	public static Character convertToCharacter(Object obj) {
		if (obj instanceof Character)
			return (Character) obj;
		else {
			try {
				return new Character(obj.toString().charAt(0));
			} catch (Exception e) {
				return null;
			}
		}
	}

	public static BigDecimal convertToBigDecimal(Object obj) {
		if (obj instanceof BigDecimal)
			return (BigDecimal) obj;
		else {
			try {
				return new BigDecimal(defaultDecimalNumberFormatter.parse(
						obj.toString()).doubleValue());
			} catch (Exception e) {
				return null;
			}
		}
	}

	public static Double convertToDouble(Object obj) {
		if (obj instanceof Double)
			return (Double) obj;
		else {
			try {
				return new Double(defaultDecimalNumberFormatter.parse(
						obj.toString()).doubleValue());
			} catch (Exception e) {
				return null;
			}
		}
	}

	public static Integer convertToInteger(Object obj) {
		if (obj instanceof Integer)
			return (Integer) obj;
		else {
			try {
				return new Integer(defaultLongNumberFormatter.parse(
						obj.toString()).intValue());
			} catch (Exception e) {
				return null;
			}
		}
	}

	public static Boolean convertToBoolean(Object obj) {
		if (obj instanceof Boolean)
			return (Boolean) obj;
		else {
			try {
				return new Boolean(obj.toString());
			} catch (Exception e) {
				return null;
			}
		}
	}

	private static DateFormat createDateFormatter(String pattern) {
		DateFormat formatter = new SimpleDateFormat(pattern);
		formatter.setLenient(false);
		return formatter;
	}

	private static String getCharacterEncoding() {
		return "UTF-8";
	}

	public static String urlEncode(String url) {
		if (url == null)
			return null;
		try {
			return URLEncoder.encode(url, getCharacterEncoding());
		} catch (UnsupportedEncodingException e) {
			logger.error("Error while trying to encode the URL!", e);
			return url;
		}
	}

	public static String urlDecode(String url) {
		if (url == null)
			return null;
		try {
			return URLDecoder.decode(url, getCharacterEncoding());
		} catch (Exception e) {
			logger.error("Error while trying to decode url" + url, e);
			return url;
		}
	}

	public static boolean shouldUrlDecode(String str) {
		return str != null && str.matches(".*%[a-zA-Z0-9]{2}.*")
				|| str.indexOf("+") != -1;
	}

	public static boolean isSafeTitleForUrl(String title) {
		return StringUtils.isBlank(title) && title.length() < 150
				&& isAllAscii(title) && !hasFormattingCharacters(title)
				&& title.indexOf("\"") == -1;
	}

	private static boolean hasFormattingCharacters(String text) {
		if (StringUtils.isBlank(text))
			return false;
		String illegalChars[] = { "+", "?", "%", "&" };
		for (int i = 0; i < illegalChars.length; i++) {
			String illegalChar = illegalChars[i];
			if (text.indexOf(illegalChar) != -1)
				return true;
		}

		return false;
	}

	public static String appendAmpsandOrQuestionMark(String str) {
		if (StringUtils.isBlank(str))
			return str;
		if (str.indexOf("?") != -1)
			return str + "&";
		else
			return str + "?";
	}

	public static String wordWrap(String str, int max) {
		if (StringUtils.isBlank(str))
			return str;
		StringBuffer sb = new StringBuffer(str);
		int nonSpaceChars = 0;
		for (int i = 0; i < sb.length(); i++) {
			if (Character.isWhitespace(sb.charAt(i)))
				nonSpaceChars = 0;
			else
				nonSpaceChars++;
			if (nonSpaceChars > max) {
				nonSpaceChars = 0;
				sb.insert(i, " ");
				i++;
			}
		}

		return sb.toString().trim();
	}

	public static String highlight(String content, String searchwords) {
		if (StringUtils.isBlank(content) || StringUtils.isBlank(searchwords))
			return content;
		StringTokenizer st = new StringTokenizer(searchwords, ", ");
		do {
			if (!st.hasMoreTokens())
				break;
			String token = st.nextToken();
			if (!token.equalsIgnoreCase("span")
					&& !token.equalsIgnoreCase("class")
					&& !token.equalsIgnoreCase("search")
					&& !token.equalsIgnoreCase("highlight"))
				content = Pattern.compile("(" + token + ")", 2)
						.matcher(content).replaceAll(
								"<span class=\"search-highlight\">$0</span>");
		} while (true);
		return content;
	}

	public static String doubleUrlEncode(String s) {
		return urlEncode(urlEncode(s));
	}

	public static boolean isAllAscii(String s) {
		char sChars[] = s.toCharArray();
		for (int i = 0; i < sChars.length; i++) {
			char sChar = sChars[i];
			if (sChar > '\177')
				return false;
		}

		return true;
	}

	public static boolean isAllLettersOrNumbers(String s) {
		char sChars[] = s.toCharArray();
		for (int i = 0; i < sChars.length; i++) {
			char sChar = sChars[i];
			if (!Character.isLetterOrDigit(sChar))
				return false;
		}

		return true;
	}

	public static String getVersionNumber() {
		return versionNumber;
	}

	public static Date getBuildDate() {
		return buildDate;
	}

	public static String getBuildDateString() {
		return format(buildDate);
	}

	public static String getBuildNumber() {
		return buildNumber;
	}

	public static String getBuildPartner() {
		return buildPartner;
	}

	public static String getBuildExcludedLocale() {
		return buildExcludedLocale;
	}

	public static boolean stringSet(String str) {
		return str != null && str.length() > 0;
	}

	public static String formatLongTime(long time) {
		StringBuffer result = new StringBuffer();
		if (time > 0x36ee80L) {
			time = scaleTime(time, 0x36ee80L, result);
			result.append(":");
		}
		time = scaleTime(time, 60000L, result);
		result.append(":");
		time = scaleTime(time, 1000L, result);
		result.append(".").append(time);
		return result.toString();
	}

	private static long scaleTime(long time, long scale, StringBuffer buf) {
		long report = time / scale;
		time -= report * scale;
		String result = Long.toString(report);
		if (report < 10L)
			result = "0" + result;
		buf.append(result);
		return time;
	}

	public static String formatDateFull(Date date) {
		return DateFormat.getDateInstance(0).format(date);
	}

	public static String formatBlogDate(Date date) {
		return blogDateFormatter.format(date) + " @ "
				+ blogTimeFormatter.format(date);
	}

	public static String escapeXml(String stringToEscape) {
		return XmlUtil.escape(stringToEscape);
	}

	public static String escapeCDATA(String s) {
		if (s.indexOf("]]") < 0)
			return s;
		else
			return s.replaceAll("\\]\\]", "]] ");
	}

	public static String unescapeCDATA(String s) {
		if (s.indexOf("]] ") < 0)
			return s;
		else
			return s.replaceAll("\\]\\] ", "]]");
	}

	public static File createTempFile(String directory) {
		Date date = new Date();
		String pattern = "_{0,date,MMddyyyy}_{1,time,HHmmss}";
		String uniqueRandomFileName = MessageFormat.format(pattern,
				new Object[] { date, date });
		return new File(directory, uniqueRandomFileName);
	}

	public static String unescapeEntities(String str) {
		Pattern hexEntityPattern = Pattern.compile("&([a-fA-F0-9]+);");
		Pattern decimalEntityPattern = Pattern.compile("&#([0-9]+);");
		str = replaceNumericEntities(str, hexEntityPattern, 16);
		return replaceNumericEntities(str, decimalEntityPattern, 10);
	}

	private static String replaceNumericEntities(String str, Pattern pattern,
			int base) {
		Matcher matcher = pattern.matcher(str);
		StringBuffer buf = new StringBuffer(str.length());
		for (; matcher.find(); matcher.appendReplacement(buf, Character
				.toString((char) Integer.parseInt(matcher.group(1), base))))
			;
		matcher.appendTail(buf);
		return buf.toString();
	}

	public static String base64Decode(String s) {
		byte sBytes[];
		String s1 = s.replaceAll("_", "/");
		String s2 = s1.replaceAll("-", "+");
		sBytes = s2.getBytes();
		try {
			return new String(Base64.decodeBase64(sBytes), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			logger
					.error("This Java installation doesn't support UTF-8. Call Mulder");
			return s;
		}
	}

	public static String base64Encode(String s) {
		try {
			byte sBytes[] = s.getBytes("UTF-8");
			return (new String(Base64.encodeBase64(sBytes))).replaceAll("\\n",
					"").replaceAll("/", "_").replaceAll("\\+", "-").trim();
		} catch (UnsupportedEncodingException e) {
			logger
					.error("This Java installation doesn't support UTF-8. Call Mulder");
			return s;
		}
	}

	public static String hackSingleQuotes(String s) {
		if (StringUtils.isNotBlank(s))
			return s.replaceAll("'", "' + '\\\\'' + '");
		else
			return s;
	}

	public static boolean isInLastDays(Date date, int maxDays) {
		if (date == null) {
			return false;
		} else {
			long tstamp = date.getTime();
			long t0 = System.currentTimeMillis();
			long dt = t0 - tstamp;
			long secs = dt / 1000L;
			long mins = secs / 60L;
			long hours = mins / 60L;
			long days = hours / 24L;
			return days < (long) maxDays;
		}
	}

	public static String getFormatDateSimple(Date date) {
		DateFormat df = new SimpleDateFormat("dd MMM");
		return df.format(date);
	}

	public static String htmlEncode(String s) {
		if (StringUtils.isBlank(s))
			return "";
		StringBuffer str = new StringBuffer();
		for (int j = 0; j < s.length(); j++) {
			char c = s.charAt(j);
			if (c < '\200')
				switch (c) {
				case 39: // '\''
					str.append("&apos;");
					break;

				case 34: // '"'
					str.append("&quot;");
					break;

				case 38: // '&'
					str.append("&amp;");
					break;

				case 60: // '<'
					str.append("&lt;");
					break;

				case 62: // '>'
					str.append("&gt;");
					break;

				default:
					str.append(c);
					break;
				}
			else
				str.append(c);
		}

		return str.toString();
	}

	public static Properties getProperties(String resource, Class callingClass) {
		return getPropertiesFromStream(ClassLoaderUtils.getResourceAsStream(
				resource, callingClass));
	}

	public static Properties getPropertiesFromFile(File file) {
		try {
			return getPropertiesFromStream(new FileInputStream(file));
		} catch (FileNotFoundException e) {
			logger.error("Error loading properties from file: " + file.getPath()
					+ ". File does not exist.", e);
			return null;
		}
	}

	public static Properties getPropertiesFromStream(InputStream is) {
		Properties props;
		if (is == null)
			return null;
		props = new Properties();
		try {
			props.load(is);
			IoUtil.close(is);
		} catch (IOException e) {
			logger.error("Error loading properties from stream.", e);
			IoUtil.close(is);
		}
		return props;
	}

	private static String extractGoogleUrl(String url, int indexOfQuery) {
		try {
			int indexOfAmpersand = url.indexOf("&", indexOfQuery);
			String googleQueryPhrase;
			if (indexOfAmpersand > -1)
				googleQueryPhrase = url.substring(indexOfQuery + 2,
						indexOfAmpersand);
			else
				googleQueryPhrase = url.substring(indexOfQuery + 2);
			url = "Google: " + URLDecoder.decode(googleQueryPhrase, "UTF-8");
		} catch (Exception e) {
		}
		return url;
	}

	public static String getUploadFileTooLargeMessage(String errorMessage) {
		Pattern p = Pattern
				.compile("java\\.io\\.IOException: Content Length Error \\((\\p{Digit}+) > (\\p{Digit}+)\\)");
		Matcher m = p.matcher(errorMessage);
		if (m.matches()) {
			String attachmentSize = FileSizeUtil.format(Long.parseLong(m
					.group(1)));
			String maxAttachmentSize = FileSizeUtil.format(Long.parseLong(m
					.group(2)));
			return "The file you are trying to upload is too large ("
					+ attachmentSize + "). Maximum attachment size allowed is "
					+ maxAttachmentSize;
		} else {
			return errorMessage;
		}
	}

	public int arraySize(int array[]) {
		return array.length;
	}

	public static String escapeXMLCharacters(String input) {
		return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
				.replaceAll(">", "&gt");
	}

	public static String htmlEscapeQuotes(String input) {
		return htmlEncode(input.replaceAll("'", "\\\\'").replaceAll("\"",
				"\\\\\""));
	}

	public static List filterNulls(Collection in) {
		List l = new ArrayList();
		Iterator i = in.iterator();
		do {
			if (!i.hasNext())
				break;
			Object o = i.next();
			if (o != null)
				l.add(o);
		} while (true);
		return l;
	}

	public static String shortenString(String str, int max) {
		if (StringUtils.isNotBlank(str) && str.length() > max)
			return str.substring(0, max) + "...";
		else
			return str;
	}

	public static List safeSubList(List list, int max) {
		if (list == null || list.size() < max)
			return list;
		else
			return list.subList(0, max);
	}

	public static ImageInfo getImageInfo(File pathToImage) {
		FileInputStream fis = null;
		if (pathToImage == null)
			return null;
		ImageInfo imageinfo;
		try {
			fis = new FileInputStream(pathToImage);
			ImageInfo imageInfo = new ImageInfo();
			imageInfo.setInput(fis);
			imageInfo.setDetermineImageNumber(true);
			imageInfo.setCollectComments(true);
			imageInfo.isValidImage();
			imageinfo = imageInfo;
			IoUtil.close(fis);
			return imageinfo;
		} catch (FileNotFoundException e) {
			logger.error("Could not load image " + pathToImage);
			imageinfo = null;
			IoUtil.close(fis);
			return imageinfo;
		}
	}

	public static String getNiceDuration(int minutes, int seconds) {
		String result = "";
		if (minutes > 0)
			result = result + minutes + " minute" + (minutes <= 1 ? "" : "s")
					+ " ";
		if (seconds > 0)
			result = result + seconds + " second" + (seconds <= 1 ? "" : "s");
		return result;
	}

	public static String personalSpaceUrl(String contextPath, String username) {
		return contextPath + "/display/~" + doubleUrlEncode(username);
	}
}
